% JS2TS post-processing to split imported files
% James Cordy, Huawei Technologies
% June 2023 (Rev. Aug 2023)

% This program takes the output of JS2TS -d NOUNEXPAND,
% and writes each of the inlined imported files to their file path
% as a TS file (e.g., import "./x" is written to file "./x.ts".

% Usage:  txl main.js js2ts.txl | txl - js2ts-split.txl > main.ts

#pragma -char -newline

% This is an island grammar to recognize js2ts inlined import blocks, 
% treating everything else as uninterpreted text lines
tokens
    % Start of the beginning and anding comment lines marking a js2ts inlined import block
    import_start  "// import "
    import_end    "// end import "
    % Any other input - any sequence of characters except //, newline, or " (the start of a stringlit)
    word_t        "#[(//)\n\"]#[(//)\n\"]*"
    % (Strings are still tokenized as [stringlit])
end tokens

% In an island grammar, each input item is either an island (in this case a js2ts inlined import block)
% or uninterpreted water (in this case, lines of uninterpreted "word"s)
define program
    [item*]
end define

define item
        [import_block]  
    |   [line]
end define

% A line is any sequence of uninterpreted "word"s followed by a newline 
define line
    [not import_start] [not import_end] [word*] [newline]
end define

define word
        [word_t]        % An uninterpreted sequence of characters not including stringlits
    |   [stringlit]     % A stringlit
end define

% The form of inlined imports in js2ts expanded output
define import_block
    % // import x.y from "originalfilepath" : "localizedfilepath"
    %       export const contents = "lots of TS source text"
    % // end import x.y from "originalfilepath" : "localizedfilepath""
    [import_start] [word*] [stringlit] [word] [stringlit] [word] [newline] [IN]
        [item*] [EX] 
    [import_end] [word*] [stringlit] [word] [stringlit] [word] [newline] 
end define

% The main rule
function main
    replace [program]
        P [program]
    by
        P [processImports]
end function

rule processImports
    % For each inlnined import block, write its converted code to its TS file,
    % and convert it to a simple import of that file
    replace [item]
        % // import x.y from "originalfilepath" : "localizedfilepath"
        _ [import_start] Imports [word*] OriginalFile [stringlit] Colon [word] LocalizedFile [stringlit] Semi [word] Newline [newline] 
            ImportedItems [item*] 
        _ [import_end] Imports OriginalFile Colon LocalizedFile Semi _ [newline]
    % Write the converted code to its TS file
    construct TSFileName [stringlit]
        LocalizedFile [strip ".js"] [+ ".ts"]
    construct _ [item*]
        ImportedItems [processImports]        % Recursively process nested import blocks
                      [write TSFileName]      % Write the converted code to its TS file
    % Replace the entire import block with an import of the new TS file
    construct ImportWord [word*]
        % There is a space after "import" in the next line that is essential, don't remove it
        'import 
    construct FileWord [word]
        OriginalFile
    by
        % // import x.y from "originalfilepath"
        ImportWord [. Imports] [. FileWord] Newline
end rule

% Utility function to strip ".js" (or any other given tail) off a file path
function strip Tail [stringlit]
    replace [stringlit]
        String [stringlit]
    construct TailM1 [number]
        _ [index String Tail] [- 1]
    by
        String [: 1 TailM1]
end function
